It’s past 1.30am. Ideally I should be in bed, but I am not. Coz I am engrossed with this issue I came across today and it took me down a rabbit hole. It’s been a while since I went down rabbit holes, but here we are!
What’s the issue? A while back I had blogged about Graph API delta queries. Essentially you can do a delta query to just get the changes to a group since the last time you made a query. Here’s the Microsoft page on it, and notice the example they give?
1 |
https://graph.microsoft.com/v1.0/groups/delta/?$filter= id eq '477e9fc6-5de7-4406-bb2a-7e5c83c9ffff' or id eq '004d6a07-fe70-4b92-add5-e6e37b8affff' |
This used to work for me in the past, but today when trying the same Graph threw errors.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
> Invoke-MgGraphRequest -Method 'GET' -Uri 'https://graph.microsoft.com/v1.0/groups/delta/?$filter= id eq ''477e9fc6-5de7-4406-bb2a-7e5c83c9ffff'' or id eq ''004d6a07-fe70-4b92-add5-e6e37b8affff''' Invoke-MgGraphRequest: GET https://graph.microsoft.com/v1.0/groups/delta/%3F%24filter%3D%2520id%2520eq%2520%27477e9fc6-5de7-4406-bb2a-7e5c83c9ffff%27%2520or%2520id%2520eq%2520%27004d6a07-fe70-4b92-add5-e6e37b8affff%27 HTTP/1.1 400 Bad Request Transfer-Encoding: chunked Vary: Accept-Encoding Strict-Transport-Security: max-age=31536000 request-id: 120bb473-9fa3-4958-8a16-0a6f3616ec08 client-request-id: 99ec7a6c-7af5-485e-a130-c693e668bc4d x-ms-ags-diagnostic: {"ServerInfo":{"DataCenter":"Canada Central","Slice":"E","Ring":"5","ScaleUnit":"002","RoleInstance":"YT2PEPF00000168"}} Date: Tue, 12 Dec 2023 01:38:14 GMT Content-Type: application/json Content-Encoding: gzip {"error":{"code":"BadRequest","message":"The request URI is not valid. The segment 'delta' must be the last segment in the URI because it is one of the following: $ref, $batch, $count, $value, $metadata, a named media resource, an action, a noncomposable function, an action import, a noncomposable function import, an operation with void return type, or an operation import with void return type.","innerError":{"date":"2023-12-12T01:38:14","request-id":"120bb473-9fa3-4958-8a16-0a6f3616ec08","client-request-id":"99ec7a6c-7af5-485e-a130-c693e668bc4d"}}} |
Huh?
This stumped me for a bit. The same URL when put into Graph Explorer worked fine, so I knew things still worked. But why was the cmdlet throwing an error?
The reason seemed to be in how it’s mangling the URL. Notice how it’s become https://graph.microsoft.com/v1.0/groups/delta/%3F%24filter%3D%2520id%2520eq%2520%27477e9fc6-5de7-4406-bb2a-7e5c83c9ffff%27%2520or%2520id%2520eq%2520%27004d6a07-fe70-4b92-add5-e6e37b8affff%27
Adding the -Debug
switch to the cmdlet too showed it was doing that.
1 2 3 4 5 6 7 |
Invoke-MgGraphRequest -Method 'GET' -Uri 'https://graph.microsoft.com/v1.0/groups/delta/?$filter= id eq ''477e9fc6-5de7-4406-bb2a-7e5c83c9ffff'' or id eq ''004d6a07-fe70-4b92-add5-e6e37b8affff''' -Debug VERBOSE: GET https://graph.microsoft.com/v1.0/groups/delta/%3F%24filter%3D%2520id%2520eq%2520%27477e9fc6-5de7-4406-bb2a-7e5c83c9ffff%27%2520or%2520id%2520eq%2520%27004d6a07-fe70-4b92-add5-e6e37b8affff%27 with 0-byte payload Confirm Continue with this operation? [Y] Yes [A] Yes to All [H] Halt Command [S] Suspend [?] Help (default is "Y"): |
My older Runbooks where I use this were working though. They were on an older version of Graph, so it could be that some newer version broke things. How do I verify this?
I didn’t want to downgrade my version of the Graph modules, nor do I have multiple VMs lying around to play with this (well, I do… but that’s not much fun). I need some way of firing up temporary instances of something where I could install different versions of the module and see where it breaks.
Enter Docker! Something I haven’t touched in ages. :)
Does Microsoft have an official Docker image with Graph modules, perhaps? Why yes, they do – but looks no one’s updated it since Graph 1.28.0.
Do they have a PowerShell image? Yes. So I could just use that as my base I suppose.
In theory things should have been straight forward here, but it’s been a while and I installed Docker in a Linux VM and ran into some trouble with the networking side (the build process couldn’t talk to the outside world). After a fair amount of late night Googling I realized I had to change the networking to host networking (as opposed to the default bridge networking which wasn’t working for me) and also add DNS settings for the Docker daemon (thanks to this post) (looks like DNS issues bit me in the past too). Long story short I got this bit working and created the following Dockerfile:
1 2 3 4 5 |
FROM mcr.microsoft.com/powershell ARG GRAPH_VERSION COPY Install-Graph.ps1 /root/ RUN pwsh /root/Install-Graph.ps1 $GRAPH_VERSION CMD [ "pwsh" ] |
I’ll explain what it does in a bit. The Install-Graph.ps1
script it refers to is:
1 2 3 4 5 6 7 8 9 10 |
param([string]$version) Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted if ($version) { Write-Output "Downloading version $version of Microsoft.Graph module" Install-Module Microsoft.Graph -RequiredVersion $version } else { Write-Output "Downloading latest version of Microsoft.Graph module" Install-Module Microsoft.Graph } |
This just downloads the version of Graph passed as a parameter to it, or the latest version if no parameter is specified. And what the Dockerfile does is basically 1) pull the Microsoft PowerShell image, 2) take the GRAPH_VERSION
variable from the arguments, if any, 3) copy the PowerShell script to the image, and 4) run it so it downloads the files.
There’s probably better ways of doing this, such as multistage builds, but this quick and dirty method suits my current requirement.
I can now build various Graph PowerShell images thus.
Say, I want to build one with the latest:
1 |
docker build --progress=plain --no-cache . --network host -t graph-latest |
Or a specific version:
1 |
docker build --progress=plain --no-cache . --network host --build-arg="GRAPH_VERSION=2.0.0" -t graph-2.0.0 |
And I can then launch these via (this is for the first image I built):
1 |
docker run -it --network host -v /path/to/.config/powershell:/root/.config/powershell:ro graph-latest |
If I then run the Graph query against the 2.0.0 version of the module I can see it works fine:
1 2 3 4 5 6 7 |
Invoke-MgGraphRequest -Method 'GET' -Uri 'https://graph.microsoft.com/v1.0/groups/delta/?$filter= id eq ''477e9fc6-5de7-4406-bb2a-7e5c83c9ffff'' or id eq ''004d6a07-fe70-4b92-add5-e6e37b8affff''' Name Value ---- ----- @odata.context https://graph.microsoft.com/v1.0/$metadata#groups @odata.nextLink https://graph.microsoft.com/v1.0/groups/delta/?$skiptoken=l-ojA6nnMNq-BLHBnOZMTzMwDcHVnE3MaXWwhuSPEymMmONnYEJlJxxu7lK… value {} |
I then went from version 2.8.0 (just a random starting point 2 months in the past) to 2.9.0 to 2.10.0 and saw that it was working fine in all these versions. Just the latest – 2.11.0 – seemed to be broken. Hah! Just my lucky day, I guess, coz 2.11.0 was released earlier today yesterday.
I thought I’d log an issue in the GitHub repo but looks like Invoke-MgGraphRequest
is broken for other calls too. Someone logged that issue just 4 hours ago… around the time I started fooling around with Docker to look into this. Nice!
Update 13th Dec: The code is on GitHub and I am publishing the container images there. See https://github.com/rakheshster/docker-powershell-msgraph.
Update 20th Feb 2024: A follow up blog post on auto-updating the Docker image. See https://rakhesh.com/linux-bsd/automatically-publishing-new-versions-of-my-graph-powershell-docker-image/.